/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/error.hpp>

namespace baja 
{

struct iservice_map : public ibase
{
BAJA_INTERFACE(iservice_map, "24D9D79A-04E7-4D2D-A5D4-6CAA767ED8E1")

    virtual void setService(
        const baja::tag& ownerTag, 
        const baja::tag& serviceTag,
        const std::shared_ptr<ibase>& service
        ) = 0;

    virtual std::shared_ptr<ibase> getService(const baja::tag& serviceTag) = 0;

    template <class Ts>
    inline std::shared_ptr<Ts> getService()
    {
        return std::dynamic_pointer_cast<Ts, ibase>(this->getService(baja::getTag<Ts>()));
    }

    template <class Towner, class Ts>
    inline void setService(const std::shared_ptr<Ts>& service)
    {
        this->setService(baja::getTag<Towner>(), baja::getTag<Ts>(), service);
    }
};

class service_map : public iservice_map
{
public:
    service_map() {}

    inline static std::shared_ptr<iservice_map> create()
    {
        return std::make_shared<service_map>();
    }

    virtual void setService(
        const baja::tag& ownerTag, 
        const baja::tag& serviceTag,
        const std::shared_ptr<ibase>& service
        )
    {
        auto it = _map.find(serviceTag);
        if (it != _map.end())
        {
            // check owner
            if (it->second.owner != ownerTag)
            {
                BAJA_THROW(std::logic_error("access denied")); // ERROR:
            }
        }

        if (service.get() != nullptr)
        {
            map_entry entry;
            entry.owner = ownerTag;
            entry.service = service;
            _map[serviceTag] = entry;
        }
        else if (it != _map.end())
        {
            _map.erase(it);
        }
    }

    virtual std::shared_ptr<ibase> getService(const baja::tag& serviceTag)
    {
        auto it = _map.find(serviceTag);
        if (it != _map.end())
        {
            return it->second.service.lock();
        }

        return std::shared_ptr<ibase>(nullptr);
    }

private:
    struct map_entry
    {
        baja::tag owner;
        std::weak_ptr<ibase> service;
    };

    std::map<baja::tag, map_entry> _map;
};

inline std::shared_ptr<iservice_map> createServiceMap()
{
    return service_map::create();
}

// singleton
template <class Bogus>
class services_
{
private:

    static std::shared_ptr<iservice_map> _map;

public:
    template <class T>
    static std::shared_ptr<T> get()
    {
        return _map->getService<T>();
    }

    template <class Towner, class Ts>
    static void set(const std::shared_ptr<Ts>& service)
    {
        _map->setService<Towner, Ts>(service);
    }

    static std::shared_ptr<iservice_map> getMap()
    {
        return _map;
    }
};

std::shared_ptr<iservice_map> services_<int>::_map = createServiceMap();

typedef services_<int> services;

} // namespace baja